package com.xc.pay.ali;

import cn.hutool.core.util.StrUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.xc.pay.AliPayProperties;
import com.xc.pay.ali.constant.AliProperties;
import com.xc.pay.ali.enums.AliMethodEnum;
import com.xc.pay.ali.enums.ProductCodeEnum;
import com.xc.pay.ali.enums.TokenEnum;
import com.xc.pay.ali.util.AliAppPayAsyncNotifyVerify;
import com.xc.pay.ali.util.VerifyPayParamUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;

/**
 * 支付宝支付相关模板接口类
 *
 * @author rongrong
 * @date 2020/11/23
 */
@Slf4j
@AllArgsConstructor
public class AliPayTemplate {

  private final AliPayProperties properties;
  private final AlipayClient alipayClient;

  /**
   * 封装数据并装载
   *
   * @param serverUrl 网关url
   * @param appId 应用APPID
   * @param appPrivateKey 应用私钥
   * @param aliPublicKey 支付宝公钥
   * @param aliPayProperties 参数类
   */
  public static AliPayTemplate build(
      String serverUrl,
      String appId,
      String appPrivateKey,
      String aliPublicKey,
      AliPayProperties aliPayProperties) {
    AlipayClient alipayClient =
        new DefaultAlipayClient(
            serverUrl,
            appId,
            appPrivateKey,
            AliProperties.FORMAT,
            AliProperties.CHARSET,
            aliPublicKey,
            AliProperties.SIGNTYPE);
    return new AliPayTemplate(aliPayProperties, alipayClient);
  }

  /**
   * 支付宝app支付
   *
   * <p>已封装掉了公共参数，这里只需要传入业务参数。 以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
   *
   * @param model 支付参数
   * @return String 数据签名
   * @date 2020-11-25
   */
  public String appPay(AlipayTradeAppPayModel model, String notifyUrl) throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradeAppPayRequest request =
        (AlipayTradeAppPayRequest) AliMethodEnum.APP_PAY_METHOD.alipayRequest;
    // 请求支付宝获取信息
    final AliPayRequestUtil<
            AlipayTradeAppPayRequest, AlipayTradeAppPayModel, AlipayTradeAppPayResponse>
        requestUtil = new AliPayRequestUtil<>(AliMethodEnum.APP_PAY_METHOD);
    AlipayTradeAppPayResponse execute =
        requestUtil.createPayRequest(alipayClient, notifyUrl, request, model);
    if (execute.isSuccess()) {
      return execute.getBody();
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 支付宝统一收单线下交易预创建(顾客主动扫码支付)
   *
   * @param model 支付参数
   * @return String 二维码url信息 用于生成二维码
   * @date 2020-11-25
   */
  public String customerNativePay(AlipayTradePrecreateModel model, String notifyUrl)
      throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradePrecreateRequest request =
        (AlipayTradePrecreateRequest) AliMethodEnum.TRADE_PRECREATE.alipayRequest;
    // 请求支付宝获取信息
    final AliPayRequestUtil<
            AlipayTradePrecreateRequest, AlipayTradePrecreateModel, AlipayTradePrecreateResponse>
        requestUtil = new AliPayRequestUtil<>(AliMethodEnum.TRADE_PRECREATE);
    AlipayTradePrecreateResponse execute =
        requestUtil.createPayRequest(alipayClient, notifyUrl, request, model);
    if (execute.isSuccess()) {
      return execute.getQrCode();
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 统一收单交易支付接口(商家扫码)
   *
   * @param model 支付参数
   * @return String success 表示成功
   * @date 2020-11-25
   */
  public String merchantNativePay(AlipayTradePayModel model, String notifyUrl)
      throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradePayRequest request =
        (AlipayTradePayRequest) AliMethodEnum.TRADE_PAY.alipayRequest;
    // 请求支付宝获取信息
    final AliPayRequestUtil<AlipayTradePayRequest, AlipayTradePayModel, AlipayTradePayResponse>
        requestUtil = new AliPayRequestUtil<>(AliMethodEnum.TRADE_PAY);
    AlipayTradePayResponse execute =
        requestUtil.createPayRequest(alipayClient, notifyUrl, request, model);
    if (execute.isSuccess()) {
      // 调用成功 等待进入回调完成业务逻辑即可
      return "success";
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 手机网站支付
   *
   * @param model 支付参数
   * @param returnUrl 支付完成后回跳地址
   * @return String 表单信息html内容
   * @date 2020-11-25
   */
  public String wapPay(AlipayTradeWapPayModel model, String notifyUrl, String returnUrl)
      throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradeWapPayRequest request =
        (AlipayTradeWapPayRequest) AliMethodEnum.TRADE_WAP_PAY.alipayRequest;
    // 请求支付宝获取信息
    final AliPayRequestUtil<
            AlipayTradeWapPayRequest, AlipayTradeWapPayModel, AlipayTradeWapPayResponse>
        requestUtil = new AliPayRequestUtil<>(AliMethodEnum.TRADE_WAP_PAY);
    // 销售产品码，商家和支付宝签约的产品码。该产品请填写固定值：QUICK_WAP_WAY
    model.setProductCode("QUICK_WAP_WAY");
    if (StrUtil.isNotBlank(returnUrl)) {
      request.setReturnUrl(returnUrl);
    }
    AlipayTradeWapPayResponse execute =
        requestUtil.createPayRequest(alipayClient, notifyUrl, request, model);
    if (execute.isSuccess()) {
      // 调用成功
      return execute.getBody();
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * PC场景支付
   *
   * @param model 支付参数
   * @return String 表单信息html内容
   * @date 2020-11-25
   */
  public String pcPay(AlipayTradePagePayModel model, String notifyUrl) throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradePagePayRequest request =
        (AlipayTradePagePayRequest) AliMethodEnum.TRADE_PAGE_PAY.alipayRequest;
    // 请求支付宝获取信息
    final AliPayRequestUtil<
            AlipayTradePagePayRequest, AlipayTradePagePayModel, AlipayTradePagePayResponse>
        requestUtil = new AliPayRequestUtil<>(AliMethodEnum.TRADE_PAGE_PAY);
    // 销售产品码，商家和支付宝签约的产品码。目前仅支持：FAST_INSTANT_TRADE_PAY
    model.setProductCode("FAST_INSTANT_TRADE_PAY");
    AlipayTradePagePayResponse execute =
        requestUtil.createPayRequest(alipayClient, notifyUrl, request, model);
    if (execute.isSuccess()) {
      // 调用成功
      return execute.getBody();
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 单笔转账接口(支持支付宝&银行卡)
   *
   * @param model 支付参数
   * @return String success 成功
   * @date 2020-11-25
   */
  public String transferAccounts(
      AlipayFundTransUniTransferModel model, ProductCodeEnum productCodeEnum, String notifyUrl)
      throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayFundTransUniTransferRequest request =
        (AlipayFundTransUniTransferRequest) AliMethodEnum.FUND_TRANS_UNI_TRANSRER.alipayRequest;
    // 请求支付宝获取信息
    final AliPayRequestUtil<
            AlipayFundTransUniTransferRequest,
            AlipayFundTransUniTransferModel,
            AlipayFundTransUniTransferResponse>
        requestUtil = new AliPayRequestUtil<>(AliMethodEnum.FUND_TRANS_UNI_TRANSRER);
    // 转账类型
    model.setProductCode(productCodeEnum.getVal());
    // DIRECT_TRANSFER：单笔无密转账到支付宝/银行卡, B2C现金红包;
    model.setBizScene("DIRECT_TRANSFER");
    AlipayFundTransUniTransferResponse execute =
        requestUtil.createPayRequest(alipayClient, notifyUrl, request, model);
    if (execute.isSuccess()) {
      // 调用成功 等待进入回调完成业务逻辑即可
      return "success";
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 用户登录授权(APP)
   *
   * @return String 调起授权字符串
   * @date 2020-11-26
   */
  public String userInfoAuth() throws AlipayApiException {
    // 加签字符串
    final String content =
        StrUtil.format(
            AliProperties.AUTH_CONTENT,
            properties.getAppid(),
            properties.getServerId(),
            System.currentTimeMillis());
    String enCodesign;
    try {
      // 签名
      String sign =
          AlipaySignature.rsaSign(
              content,
              properties.getAppPrivateKey(),
              AliProperties.CHARSET,
              AliProperties.SIGNTYPE);
      enCodesign = URLEncoder.encode(sign, "UTF-8");
    } catch (Exception e) {
      e.printStackTrace();
      throw new AlipayApiException(e.getMessage());
    }
    return content + "&sign=" + enCodesign;
  }

  /**
   * 通过code获取token
   *
   * @param codeOrRefreshToken code或者refreshToken
   * @param tokenEnum 获取类型
   */
  public AlipaySystemOauthTokenResponse getTokenOrRereshToken(
      String codeOrRefreshToken, TokenEnum tokenEnum) throws AlipayApiException {
    AlipaySystemOauthTokenRequest request =
        (AlipaySystemOauthTokenRequest) AliMethodEnum.GET_TOKEN.alipayRequest;
    // 值为authorization_code时，代表用code换取；值为refresh_token时，代表用refresh_token换取
    request.setGrantType(tokenEnum.getCode());
    if (tokenEnum.isRefresh()) {
      // 刷新令牌，上次换取访问令牌时得到,即：AccessToken
      request.setRefreshToken(codeOrRefreshToken);
    } else {
      // 授权码，用户对应用授权后得到。
      request.setCode(codeOrRefreshToken);
    }
    try {
      return alipayClient.execute(request);
    } catch (AlipayApiException e) {
      // 处理异常
      e.printStackTrace();
      throw new AlipayApiException(e.getMessage());
    }
  }

  /**
   * 支付宝授权获取用户信息(APP)
   *
   * @return String 调起授权字符串
   * @date 2020-11-26
   */
  public AlipayUserInfoShareResponse getUserInfo(String code) throws AlipayApiException {
    // 换取token
    final AlipaySystemOauthTokenResponse token =
        getTokenOrRereshToken(code, TokenEnum.AUTHORIZATION_CODE);
    if (!token.isSuccess()) {
      throw new AlipayApiException(token.getSubMsg());
    }
    // 获取用户信息
    AlipayUserInfoShareRequest request =
        (AlipayUserInfoShareRequest) AliMethodEnum.GET_USER_INFO.alipayRequest;
    AlipayUserInfoShareResponse response = alipayClient.execute(request, token.getAccessToken());
    if (response.isSuccess()) {
      return response;
    } else {
      throw new AlipayApiException(response.getSubMsg());
    }
  }

  /**
   * 交易退款
   *
   * @param model 参数信息 out_request_no 参数不可为空，请记录 在退款查询时需用到
   * @return AlipayTradeRefundResponse
   * @throws AlipayApiException 异常信息
   */
  public AlipayTradeRefundResponse refundOrder(AlipayTradeRefundModel model)
      throws AlipayApiException {
    AlipayTradeRefundRequest request =
        (AlipayTradeRefundRequest) AliMethodEnum.TRADE_REFUND_METHOD.alipayRequest;
    // 必填项校验
    if (StrUtil.isAllBlank(model.getOutTradeNo(), model.getTradeNo())) {
      throw new AlipayApiException("out_trade_no与trade_no不可同时为空");
    }
    if (StrUtil.isBlank(model.getOutRequestNo())) {
      throw new AlipayApiException("out_request_no参数不可为空");
    }
    VerifyPayParamUtil.verifyTotalAmount(model.getRefundAmount());
    request.setBizModel(model);
    try {
      // 发送退款请求
      AlipayTradeRefundResponse response = alipayClient.execute(request);
      if (response.isSuccess()) {
        return response;
      }
      throw new AlipayApiException(response.getSubCode(), response.getSubMsg());
    } catch (AlipayApiException e) {
      e.printStackTrace();
      throw new AlipayApiException(e.getMessage());
    }
  }

  /**
   * 订单查询
   *
   * @param model 参数信息
   * @return AlipayTradeRefundResponse
   * @throws AlipayApiException 异常信息
   */
  public AlipayTradeQueryResponse queryOrderInfo(AlipayTradeQueryModel model)
      throws AlipayApiException {
    AlipayTradeQueryRequest request =
        (AlipayTradeQueryRequest) AliMethodEnum.TRADE_QUERY_METHOD.alipayRequest;
    // 必填项校验
    if (StrUtil.isAllBlank(model.getOutTradeNo(), model.getTradeNo())) {
      throw new AlipayApiException("out_trade_no与trade_no不可同时为空");
    }
    request.setBizModel(model);
    try {
      // 订单查询
      AlipayTradeQueryResponse response = alipayClient.execute(request);
      if (response.isSuccess()) {
        return response;
      }
      throw new AlipayApiException(response.getSubCode(), response.getSubMsg());
    } catch (AlipayApiException e) {
      e.printStackTrace();
      throw new AlipayApiException(e.getMessage());
    }
  }

  /**
   * 订单创建(指定买家) 结合JSAPI调用进行完成支付
   *
   * @param model 支付参数
   * @return AlipayTradeCreateResponse 商户订单号以及支付宝交易号
   * @date 2020-11-25
   */
  public AlipayTradeCreateResponse tardeCreate(AlipayTradeCreateModel model, String notifyUrl)
      throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradeCreateRequest request =
        (AlipayTradeCreateRequest) AliMethodEnum.TRADE_CREATE.alipayRequest;
    // 请求支付宝获取信息
    final AliPayRequestUtil<
            AlipayTradeCreateRequest, AlipayTradeCreateModel, AlipayTradeCreateResponse>
        requestUtil = new AliPayRequestUtil<>(AliMethodEnum.TRADE_CREATE);
    AlipayTradeCreateResponse execute =
        requestUtil.createPayRequest(alipayClient, notifyUrl, request, model);
    if (execute.isSuccess()) {
      return execute;
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 订单关闭
   *
   * @param model 支付参数
   * @return AlipayTradeCreateResponse 商户订单号以及支付宝交易号
   * @date 2020-11-25
   */
  public AlipayTradeCloseResponse tardeClose(AlipayTradeCloseModel model)
      throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradeCloseRequest request =
        (AlipayTradeCloseRequest) AliMethodEnum.TRADE_CLOSE.alipayRequest;
    request.setBizModel(model);
    // 请求支付宝获取信息
    AlipayTradeCloseResponse execute = alipayClient.execute(request);
    if (execute.isSuccess()) {
      return execute;
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 订单结算接口 （进行分账）,分账金额最高不超过订单总金额30% 参考：https://opendocs.alipay.com/open/20190308105425129272/intro
   *
   * @param model 支付参数
   * @return String 支付宝交易号
   * @date 2020-11-25
   */
  public String tardeOrderSettle(AlipayTradeOrderSettleModel model) throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradeOrderSettleRequest request =
        (AlipayTradeOrderSettleRequest) AliMethodEnum.TRADE_ORDER_SETTLE.alipayRequest;
    request.setBizModel(model);
    // 请求支付宝获取信息
    AlipayTradeOrderSettleResponse execute = alipayClient.execute(request);
    if (execute.isSuccess()) {
      return execute.getTradeNo();
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 订单退款查询 该接口的返回码10000，仅代表本次查询操作成功，不代表退款成功。 如果该接口返回了查询数据，且refund_status为空或为REFUND_SUCCESS，则代表退款成功，
   * 如果没有查询到则代表未退款成功，可以调用退款接口进行重试。重试时请务必保证退款请求号一致。
   *
   * <p>refund_status为空时一定要在校验其他参数 例如 refund_amount 或者 交易号等信息 需要验证有查询到订单信息。
   *
   * @param model 参数
   * @return AlipayTradeFastpayRefundQueryResponse 响应结果
   * @date 2020-11-25
   */
  public AlipayTradeFastpayRefundQueryResponse refundQuery(AlipayTradeFastpayRefundQueryModel model)
      throws AlipayApiException {
    // 实例化具体API对应的request类
    final AlipayTradeFastpayRefundQueryRequest request =
        (AlipayTradeFastpayRefundQueryRequest) AliMethodEnum.TRADE_REFUND_QUERY.alipayRequest;
    // 必填项校验
    if (StrUtil.isAllBlank(model.getOutTradeNo(), model.getTradeNo())) {
      throw new AlipayApiException("out_trade_no与trade_no不可同时为空");
    }
    if (StrUtil.isBlank(model.getOutRequestNo())) {
      throw new AlipayApiException("out_request_no不可为空");
    }
    request.setBizModel(model);
    // 请求支付宝获取信息
    AlipayTradeFastpayRefundQueryResponse execute = alipayClient.execute(request);
    if (execute.isSuccess()) {
      return execute;
    }
    throw new AlipayApiException(execute.getSubMsg());
  }

  /**
   * 支付回调验签
   *
   * @param request 参数
   * @param isChangeCharSet 是否存在乱码
   * @return 是否验签成功
   */
  public boolean appPayAnyscVerify(HttpServletRequest request, boolean isChangeCharSet)
      throws AlipayApiException {
    return AliAppPayAsyncNotifyVerify.alipayNotify(
        request, isChangeCharSet, properties.getAliPublicKey());
  }
}
